﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.ObjectModel;
using System.Windows;

namespace Kokomo.PeInspector
{
	public abstract class SearchViewModel<TResult> : NotifierObject
	{
		public static readonly TimeSpan DefaultDelay = TimeSpan.FromMilliseconds(200);

		public SearchViewModel()
		{
			this.ResultChunkSize = 1;

			this.Delay = DefaultDelay;
			this._searchTimer = new Timer(this.SearchAsyncProc, null, Timeout.Infinite, Timeout.Infinite);
		}

		private Timer _searchTimer;

		public TimeSpan Delay { get; private set; }
		protected int ResultChunkSize { get; set; }

		private bool _requiresExactMatch;
		public bool RequiresExactMatch
		{
			get { return this._requiresExactMatch; }
			set
			{
				this._requiresExactMatch = value;
				this.InvalidateSearch();
				this.OnPropertyChanged("RequiresExactMatch");
			}
		}

		private bool _isCaseSensitive;
		public bool IsCaseSensitive
		{
			get { return this._isCaseSensitive; }
			set
			{
				this._isCaseSensitive = value;
				this.InvalidateSearch();
				this.OnPropertyChanged("IsCaseSensitive");
			}
		}

		private string _searchTerm;
		public string SearchTerm
		{
			get { return this._searchTerm; }
			set
			{
				this._searchTerm = value;
				this.OnSearchTermChanged();
			}
		}
		protected virtual void OnSearchTermChanged()
		{
			this.InvalidateSearch();
			this.OnPropertyChanged("SearchTerm");
		}
		public void InvalidateSearch()
		{
			this.BeginSearch();
		}

		protected int SearchVersion { get; private set; }
		protected void BeginSearch()
		{
			this.SearchVersion++;
			this._searchTimer.Change(this.Delay.Milliseconds, -1);
			//ThreadPool.QueueUserWorkItem(this.SearchAsyncProc, this._searchTerm);
		}

		private bool _isSearching;
		public bool IsSearching
		{
			get { return this._isSearching; }
			set
			{
				this._isSearching = value;
				this.OnPropertyChanged("IsSearching");
			}
		}

		private ProgressInfo _searchProgress;
		public ProgressInfo SearchProgress
		{
			get { return this._searchProgress; }
			set
			{
				this._searchProgress = value;
				this.OnPropertyChanged("SearchProgress");
			}
		}

		private void SearchAsyncProc(object arg)
		{
			string searchTerm = this.SearchTerm;

			int searchVersion = this.SearchVersion;
			//Thread.Sleep(500);

			//if (this.SearchVersion != searchVersion) return;

			this.SearchProgress = new ProgressInfo();
			this.IsSearching = true;
			try
			{
				IEnumerable<TResult> results = this.OnSearchAsync(searchTerm, searchVersion);
				if (this.SearchVersion == searchVersion)
				{
					this.DisplaySearchResults(results, searchVersion);
				}
			}
			catch { }

			if (this.SearchVersion == searchVersion)
			{
				this.IsSearching = false;
			}
		}

		protected abstract IEnumerable<TResult> OnSearchAsync(
			string searchTerm,
			int searchVersion
			);
		protected virtual void OnResultsChanging(IEnumerable<TResult> results)
		{
		}

		private void DisplaySearchResults(
			IEnumerable<TResult> results,
			int searchVersion
			)
		{
			if (results == null)
			{
				this.Results = null;
			}
			else if (results is IList<TResult>)
			{
				this.Results = results;
			}
			else
			{
				//lock (this._searchResults)
				{
					ObservableCollection<TResult> resultList = new ObservableCollection<TResult>();
					this.Results = resultList;

					//this._searchResults.DispatcherBeginClear();
					int chunkSize = Math.Max(1, this.ResultChunkSize);
					if (chunkSize == 1)
					{
						foreach (var item in results)
						{
							if (this.SearchVersion != searchVersion) break;
							resultList.DispatcherAdd(item);
						}
					}
					else
					{
						List<TResult> chunk = new List<TResult>(chunkSize);
						foreach (var item in results)
						{
							if (this.SearchVersion != searchVersion) break;
							chunk.Add(item);
							if (chunk.Count >= chunkSize)
							{
								resultList.DispatcherAddRange(chunk);
								chunk.Clear();
							}
						}
						if (chunk.Count > 0)
						{
							resultList.DispatcherAddRange(chunk);
						}
					}
				}
			}
		}

		private IEnumerable<TResult> _results;
		public IEnumerable<TResult> Results
		{
			get
			{
				this.EnsureResults();
				return this._results;
			}
			set
			{
				if (value == this._results) return;

				Application.Current.Dispatcher.Invoke(
					(Action<IEnumerable<TResult>>)this.OnResultsChanging,
					value
					);
				//this.OnResultsChanging(value);
				this._results = value;
				this.OnPropertyChanged("Results");
			}
		}
		private void EnsureResults()
		{
			if (this._results == null && !this.IsSearching)
			{
				this.BeginSearch();
			}
		}
	}
}
